package com.young.requestlimit.advice;

import com.young.requestlimit.annotation.RequestLimit;
import com.young.requestlimit.common.ResponseEnum;
import com.young.requestlimit.config.RequestLimitConfig;
import com.young.requestlimit.exception.RequestLimitException;
import org.redisson.Redisson;
import org.redisson.api.RAtomicLong;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.Optional;

/**
 * @Author mouyang
 * @Date 2023/09/21 15:56
 **/
@ControllerAdvice
@Order(999)
public class RequestLimitAdvice extends RequestBodyAdviceAdapter {

    @Resource
    private Redisson redisson;

    @Resource
    private RequestLimitConfig requestLimitConfig;

    private static final String REDIS_KEY_PREFIX = "requestLimit:";

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        boolean support = Boolean.parseBoolean(requestLimitConfig.getEnable());
        if (support) {
            support = methodParameter.getMethod().isAnnotationPresent(RequestLimit.class);
        }
        return support;
    }


    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        String key = REDIS_KEY_PREFIX + getCurrentRequest().getQueryString();
        final RAtomicLong atomicLong = redisson.getAtomicLong(key);
        long lock = atomicLong.getAndIncrement();
        if (lock > 0) {
            throw new RequestLimitException("重复请求");
        } else {
            Long time = Optional.ofNullable(getMethod(parameter).getAnnotation(RequestLimit.class))
                    .map(RequestLimit::limitTime)
                    .orElse(requestLimitConfig.getDefaultLimitTime());
            atomicLong.expireAsync(Duration.of(time, ChronoUnit.MILLIS));
        }
        return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
    }

    private HttpServletRequest getCurrentRequest() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            throw new RuntimeException(ResponseEnum.EXCEPTION.getMsg());
        }
        return ((ServletRequestAttributes) requestAttributes).getRequest();
    }

    private Method getMethod(MethodParameter parameter) {
        Method method = parameter.getMethod();
        if (method == null) {
            throw new RuntimeException(ResponseEnum.EXCEPTION.getMsg());
        }
        return method;
    }


}
